package com.chaos.framework.model.helper;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;


/**
 * Title: ListKit
 * Description: list工具类
 *
 * @author 肖晓霖
 * @since 2019年12月23日
 */
public final class ListHelper {

    private ListHelper() {
        super();
    }

    /**
     * 查找列表中第一个满足给定谓词的元素的索引。
     *
     * @param list      需要搜索的列表，不能为空。
     * @param predicate 用于测试列表中元素的谓词，不能为空。
     * @param <E>       列表中元素的类型。
     * @return 返回满足谓词的第一个元素的索引；如果没有找到满足条件的元素，则返回-1。
     */
    public static <E> int findIndex(List<E> list, Predicate<E> predicate) {
        Assert.notNull(predicate);
        Assert.notNull(list);
        // 遍历列表，使用谓词测试每个元素
        for (int i = 0; i < list.size(); i++) {
            // 如果元素满足谓词，则返回该元素的索引
            if (predicate.test(list.get(i))) {
                return i;
            }
        }
        // 如果没有元素满足谓词，则返回-1
        return -1;
    }


    /**
     * 从列表中查找满足特定条件的元素
     *
     * @param list      待搜索的列表
     * @param predicate 用于判断元素是否满足条件的断言
     * @param <E>       列表元素的类型
     * @return 如果找到满足条件的元素，则返回该元素的Optional，否则返回Optional.empty()
     */
    public static <E> Optional<E> find(List<E> list, Predicate<E> predicate) {
        // 遍历列表中的每个元素
        for (E e : list) {
            // 如果当前元素满足条件，则返回该元素的Optional
            if (predicate.test(e)) {
                return Optional.ofNullable(e);
            }
        }
        // 如果没有找到满足条件的元素，则返回Optional.empty()
        return Optional.empty();
    }


    /**
     * 查找列表中第一个重复的元素，基于指定的关键字操作。
     *
     * @param list     要搜索的列表，其元素可以是任意类型。
     * @param operator 一个函数，它接受列表中的元素并将其转换为关键字（字符串类型）。
     * @param <E>      列表中元素的类型。
     * @return 如果找到重复的元素，则返回包含该元素的Optional；如果没有找到重复元素，则返回空Optional。
     */
    public static <E> Optional<E> findFirstRepeatElement(List<E> list, Function<E, String> operator) {
        if (ObjectUtil.isEmpty(list)) {
            return Optional.empty();
        }
        HashSet<String> set = Sets.newHashSetWithExpectedSize(list.size());
        for (E e : list) {
            String key = operator.apply(e);
            if (set.contains(key)) {
                return Optional.ofNullable(e);
            }
            set.add(key);
        }
        return Optional.empty();
    }


    /**
     * 检查列表中是否存在重复元素
     * 此方法通过指定的函数将列表元素转换为字符串，然后比较转换后的字符串来判断是否存在重复元素
     *
     * @param list     要检查的列表
     * @param operator 一个函数，用于将列表中的元素转换为字符串
     * @param <E>      列表元素的类型
     * @return 如果列表中存在至少一个重复元素，则返回true；否则返回false
     */
    public static <E> boolean existRepeatElement(List<E> list, Function<E, String> operator) {
        // 如果列表为空，则直接返回false，因为没有元素可以重复
        if (ObjectUtil.isEmpty(list)) {
            return false;
        }
        // 创建一个HashSet，用于存储转换后的字符串，HashSet自动保证元素的唯一性
        HashSet<String> set = Sets.newHashSetWithExpectedSize(list.size());
        // 遍历列表中的每个元素
        for (E e : list) {
            // 使用提供的函数将元素转换为字符串
            String key = operator.apply(e);
            // 如果Set中已经包含此字符串，则说明存在重复元素，返回true
            if (set.contains(key)) {
                return true;
            }
            // 将转换后的字符串添加到Set中
            set.add(key);
        }
        // 如果遍历完所有元素都没有发现重复，说明列表中没有重复元素，返回false
        return false;
    }


    /**
     * 使用给定的谓词过滤列表中的元素。
     * <p>
     * 此方法不会修改原始列表，而是返回一个新的列表，该列表仅包含满足谓词条件的元素。
     *
     * @param list      要过滤的列表，它可以是任意类型的 E 的列表。
     * @param predicate 用于过滤列表的谓词，这是一个确定元素是否应包含在结果中的条件。
     * @return 一个新列表，其中仅包含满足谓词条件的元素。
     * @throws IllegalArgumentException 如果谓词为 null，则会抛出 IllegalArgumentException。
     */
    public static <E> List<E> filter(List<E> list, Predicate<E> predicate) {
        Preconditions.checkNotNull(predicate, "predicate cannot be null");
        if (ObjectUtil.isEmpty(list)) {
            return list;
        }
        return list.stream().filter(predicate).collect(Collectors.toList());
    }

    public static <E, R> List<R> convert(List<E> list, Function<E, R> operator) {
        return getField(list, operator);
    }

    public static <E, R> List<R> convert(List<E> list, BiFunction<E, Integer, R> operator) {
        if (ObjectUtil.isEmpty(list)) {
            return Lists.newArrayList();
        }
        List<R> resultList = new ArrayList<>(list.size());
        forEach(list, (item, index) -> {
            final R value = operator.apply(item, index);
            if (Objects.isNull(value)) {
                return;
            }
            resultList.add(value);
        });
        return resultList;
    }


    public static <E, R> List<R> getField(List<E> list, Function<E, R> operator) {
        if (ObjectUtil.isEmpty(list)) {
            return Lists.newArrayList();
        }
        List<R> fields = new ArrayList<>(list.size());
        list.forEach(item -> {
            final R value = operator.apply(item);
            if (Objects.isNull(value)) {
                return;
            }
            fields.add(value);
        });
        return fields;
    }

    public static <E, R> List<R> getDistinctField(List<E> list, Function<E, R> operator) {
        List<R> fields = getField(list, operator);
        return distinct(fields, Function.identity());
    }

    public static <E, R> List<R> getFieldFlat(List<E> list, Function<E, List<R>> operator) {
        return ListHelper.getField(list, operator).stream().flatMap(java.util.Collection::stream).collect(Collectors.toList());
    }

    public static <E, R> Set<R> getFieldSet(List<E> list, Function<E, R> operator) {
        Set<R> fieldSet = Sets.newHashSetWithExpectedSize(list.size());
        if (CollectionUtil.isEmpty(list)) {
            return fieldSet;
        }
        list.forEach(item -> {
            final R value = operator.apply(item);
            if (Objects.isNull(value)) {
                return;
            }
            fieldSet.add(value);
        });
        return fieldSet;
    }

    public static <E> String getFieldStr(List<E> list, Function<E, String> operator) {
        return getFieldStr(list, operator, ",");
    }

    public static <E> String getFieldStr(List<E> list, Function<E, String> operator, String split) {
        if (ObjectUtil.isEmpty(list)) {
            return "";
        }
        if (StrUtil.isEmpty(split)) {
            split = ",";
        }
        StringBuilder sb = new StringBuilder();
        for (E item : list) {
            final String value = operator.apply(item);
            if (StrUtil.isEmpty(value)) {
                continue;
            }
            sb.append(value).append(split);
        }
        if (sb.length() == 0) {
            return "";
        }
        return sb.delete(sb.length() - 1, sb.length()).toString();
    }

    public static <K, E> Map<K, E> convertToMap(List<E> list, Function<E, K> operator) {
        if (ObjectUtil.isEmpty(list)) {
            return Maps.newHashMap();
        }
        Map<K, E> resultMap = Maps.newHashMapWithExpectedSize(list.size());
        list.forEach(item -> {
            final K key = operator.apply(item);
            if (ObjectUtil.isEmpty(key)) {
                return;
            }
            resultMap.put(key, item);
        });
        return resultMap;
    }

    public static <K, V, E> Map<K, V> convertToMap(List<E> list, Function<E, K> keyFunction, Function<E, V> valueFunction) {
        if (ObjectUtil.isEmpty(list)) {
            return Maps.newHashMap();
        }
        Map<K, V> resultMap = Maps.newHashMapWithExpectedSize(list.size());
        list.forEach(item -> {
            resultMap.put(keyFunction.apply(item), valueFunction.apply(item));
        });
        return resultMap;
    }

    public static <E, K> Map<K, E> convert2Map(List<E> list, Function<E, K> operator) {
        if (ObjectUtil.isEmpty(list)) {
            return Maps.newHashMap();
        }
        Map<K, E> resultMap = Maps.newHashMapWithExpectedSize(list.size());
        list.forEach(item -> {
            final K key = operator.apply(item);
            resultMap.put(key, item);
        });
        return resultMap;
    }

    public static <K, E> List<E> merge(List<E> list, Function<E, K> keyFunction, BiFunction<E, E, E> mergeFunction) {
        Map<K, E> map = mergeToMap(list, keyFunction, (a, b) -> {
            if (Objects.isNull(b)) {
                return a;
            } else {
                return mergeFunction.apply(a, b);
            }
        });
        return Lists.newArrayList(map.values());
    }

    public static <K, E> List<E> merge(List<E> list, Function<E, K> keyFunction, BiFunction<E, E, E> mergeFunction, Function<E, E> initValFunction) {
        Map<K, E> map = merge2Map(list, keyFunction, mergeFunction, initValFunction);
        return Lists.newArrayList(map.values());
    }

    public static <K, E, R> Map<K, R> mergeToMap(List<E> list, Function<E, K> keyFunction, BiFunction<E, R, R> mergeFunction) {
        Map<K, R> map = Maps.newLinkedHashMap();
        if (ObjectUtil.isEmpty(list)) {
            return map;
        }
        list.forEach(item -> {
            K key = keyFunction.apply(item);
            map.compute(key, (k, target) -> mergeFunction.apply(item, target));
        });
        return map;
    }

    public static <K, E, R> Map<K, R> merge2Map(List<E> list, Function<E, K> keyFunction, BiFunction<E, R, R> mergeFunction, Function<E, R> initValFunction) {
        Map<K, R> map = Maps.newLinkedHashMap();
        if (ObjectUtil.isEmpty(list)) {
            return map;
        }
        list.forEach(item -> {
            K key = keyFunction.apply(item);
            R target = map.get(key);
            if (Objects.isNull(target)) {
                target = initValFunction.apply(item);
                map.put(key, target);
            } else {
                map.put(key, mergeFunction.apply(item, target));
            }
        });
        return map;
    }

    public static <K, E, R> Map<K, R> mergeToMap(List<E> list, Function<E, K> keyFunction, BiFunction<E, R, R> mergeFunction, R initValue) {
        Map<K, R> map = Maps.newLinkedHashMap();
        if (ObjectUtil.isEmpty(list)) {
            return map;
        }
        list.forEach(item -> {
            K key = keyFunction.apply(item);
            R target = map.get(key);
            if (Objects.isNull(target)) {
                target = initValue;
            }
            map.put(key, mergeFunction.apply(item, target));
        });
        return map;
    }

    public static <K, E, R> Map<K, R> merge2Map(List<E> list, Function<E, K> keyFunction, BiFunction<E, R, R> mergeFunction, Supplier<R> initValFunction) {
        Map<K, R> map = Maps.newLinkedHashMap();
        if (ObjectUtil.isEmpty(list)) {
            return map;
        }
        list.forEach(item -> {
            K key = keyFunction.apply(item);
            R target = map.get(key);
            if (Objects.isNull(target)) {
                target = initValFunction.get();
            }
            map.put(key, mergeFunction.apply(item, target));
        });
        return map;
    }

    public static <K, E> Map<K, List<E>> groupToMap(List<E> list, Function<E, K> keyFunction) {
        Map<K, List<E>> map = Maps.newLinkedHashMap();
        if (ObjectUtil.isEmpty(list)) {
            return map;
        }
        list.forEach(item -> {
            K key = keyFunction.apply(item);
            List<E> target = map.get(key);
            if (Objects.isNull(target)) {
                target = Lists.newLinkedList();
                map.put(key, target);
            }
            target.add(item);
        });
        return map;
    }

    public static <K, E> Map<K, List<E>> group(List<E> list, Function<E, K> keyFunction) {
        return groupToMap(list, keyFunction);
    }

    public static <K, E, R> Map<K, List<R>> groupToMap(List<E> list, Function<E, K> keyFunction, Function<E, R> valueFunction) {
        Map<K, List<R>> map = Maps.newLinkedHashMap();
        if (ObjectUtil.isEmpty(list)) {
            return map;
        }
        list.forEach(item -> {
            K key = keyFunction.apply(item);
            List<R> target = map.get(key);
            if (Objects.isNull(target)) {
                target = Lists.newLinkedList();
                map.put(key, target);
            }
            R value = valueFunction.apply(item);
            if (Objects.nonNull(value)) {
                target.add(value);
            }
        });
        return map;
    }

    public static <K, E> List<E> distinct(List<E> list, Function<E, K> keyFunction) {
        Map<K, E> linkedMap = Maps.newLinkedHashMap();
        for (E item : list) {
            K key = keyFunction.apply(item);
            linkedMap.put(key, item);
        }
        return Lists.newArrayList(linkedMap.values());
    }

    public static <E1, E2, K> void setGroup(List<E1> list1, Function<E1, K> key1Function, List<E2> list2, Function<E2, K> key2Function, BiConsumer<E1, List<E2>> setConsumer) {
        if (!ObjectUtil.isAllNotEmpty(list1, list2)) {
            return;
        }
        Map<K, List<E2>> list2Map = groupToMap(list2, key2Function);
        for (E1 e1 : list1) {
            K key = key1Function.apply(e1);
            List<E2> l2 = list2Map.get(key);
            if (ObjectUtil.isNotEmpty(l2)) {
                setConsumer.accept(e1, l2);
            }
        }
    }

    public static <E1, E2, K1> void set(List<E1> list1, Function<E1, K1> key1Function, List<E2> list2, Function<E2, K1> key2Function, BiConsumer<E1, E2> setConsumer) {
        if (!ObjectUtil.isAllNotEmpty(list1, list2)) {
            return;
        }
        Map<K1, E2> list2Map = convert2Map(list2, key2Function);
        for (E1 e1 : list1) {
            K1 key = key1Function.apply(e1);
            E2 e2 = list2Map.get(key);
            if (ObjectUtil.isNotEmpty(e2)) {
                setConsumer.accept(e1, e2);
            }
        }
    }

    public static <E> void forEach(List<E> list, BiConsumer<E, Integer> consumer) {
        if (ObjectUtil.isEmpty(list)) {
            return;
        }
        for (int i = 0, size = list.size(); i < size; i++) {
            consumer.accept(list.get(i), i);
        }
    }

    public static <K, E, R> List<R> groupAndConvert(List<E> list, Function<E, K> keyFunction, BiFunction<K, List<E>, R> operator) {
        return MapHelper.convertToList(groupToMap(list, keyFunction), operator);
    }


    public static <T> T findFirst(List<T> list, Predicate<T> predicate) {
        if (ObjectUtil.isEmpty(list)) {
            return null;
        }
        return list.stream().filter(predicate).findFirst().orElse(null);
    }

    /**
     * 根据一系列谓词从列表中找到最匹配的元素。
     * 此方法接受一个元素列表和一个谓词列表作为输入。谓词列表包含了一系列用于测试元素的条件。
     * 方法的目的是在元素列表中找到一个元素，该元素满足所有给定的谓词条件。如果存在多个满足所有条件的元素，
     * 则返回第一个找到的元素。如果没有元素满足所有条件，则返回null。
     *
     * @param <T>        被搜索元素和谓词操作的泛型类型。
     * @param list       包含待搜索元素的列表。
     * @param predicates 一个包含用于测试元素的谓词的列表。
     * @return 列表中满足所有谓词条件的第一个元素，如果不存在则返回null。
     */
    public static <T> T findBestMatch(List<T> list, List<Predicate<T>> predicates) {
        if (ObjectUtil.isEmpty(list)) {
            return null;
        }
        for (Predicate<T> predicate : predicates) {
            T t = list.stream().filter(predicate).findFirst().orElse(null);
            if (Objects.nonNull(t)) {
                return t;
            }
        }
        return null;
    }

    public static <T> T findBestMatch(List<T> list, Predicate<T> p1) {
        return findBestMatch(list, Collections.singletonList(p1));
    }

    @SafeVarargs
    public static <T> T findBestMatch(List<T> list, final Predicate<T>... predicates) {
        return findBestMatch(list, Arrays.asList(predicates));
    }

    public static <E> E findBestMatch(List<E> list, Predicate<E> p1, Predicate<E> p2) {
        return findBestMatch(list, Arrays.asList(p1, p2));
    }

    public static <T> T findBestMatch(List<T> list, Predicate<T> p1, Predicate<T> p2, Predicate<T> p3) {
        return findBestMatch(list, Arrays.asList(p1, p2, p3));
    }
}
